package org.peppa.utils;



import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortInvalidPortException;
import lombok.extern.slf4j.Slf4j;
import org.peppa.jna.CRC16ModbusUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TooManyListenersException;


/**
 * @Projectname: ModeBusPortCommunication
 * @Filename: ModBusCommandUtil
 * @Author: FANSEA
 * @Data:2024/7/8 14:56
 */
public class ModBusCommandUtil {


    /**
     * 打开电脑上指定的串口
     *
     * @param portName 端口名称，如 COM1，为 null 时，默认使用电脑中能用的端口中的第一个
     * @param baudRate        波特率(baudrate)，如 9600
     * @param dataBits        数据位（datebits），如 SerialPort.DATABITS_8 = 8
     * @param stopBits        停止位（stopbits），如 SerialPort.STOPBITS_1 = 1
     * @param parity          校验位 (parity)，如 SerialPort.PARITY_NONE = 0
     * @return 打开的串口对象，打开失败时，返回 null
     */
    public static SerialPort openPort(String portName,Integer baudRate, Integer dataBits, Integer stopBits, Integer parity) {

        SerialPort serialPort = SerialPort.getCommPort(portName);
        try {
            if (serialPort.isOpen()){
                throw new RuntimeException("串口已经打开，请先关闭再打开!");
            }
            if (!serialPort.openPort()){
                throw new RuntimeException("串口打开失败，请检查串口是否被占用并保证主机包含该端口!: portName = " + portName);
            }
        } catch (SerialPortInvalidPortException e){
            throw new RuntimeException("串口名称不正确，请检查串口名称是否正确: portName = " + portName);
        }
        serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity);
        serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0);
        return serialPort;
    }



    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString().trim();
    }

    public static List<String> getCurrentSysPortName() {
        List<String> portName = new ArrayList<String>();
        for (SerialPort commPort : SerialPort.getCommPorts()) {
            portName.add(commPort.getSystemPortName());
        }
        return portName;
    }

    public static void closePort(SerialPort serialPort) {
        serialPort.closePort();
    }

    /**
     * 给串口设置监听
     */
    public static void setListenerToSerialPort(SerialPort serialPort, SerialPortDataListener listener) throws TooManyListenersException {
        //serialPort.removeEventListener();
        //给串口添加事件监听
        serialPort.addDataListener(listener);
    }

    /**
     * @description: 发送WRITE指令
     * @author: fansea 
     * @date: 2024/7/29 14:07
     * @param: [serialPort, writeBuffer]
     * @return: void
     **/
    public static void sendWriteCommand(SerialPort serialPort, byte[] writeBuffer) {
        System.out.println("发送写指令: " + bytesToHex(writeBuffer));
        if (serialPort == null) {
            throw new RuntimeException("串口未打开！");
        }
        serialPort.writeBytes(writeBuffer, writeBuffer.length);
        byte[] bytes = new byte[writeBuffer.length];
        serialPort.readBytes(bytes, bytes.length);
        if (!Arrays.equals(bytes, writeBuffer)){
            throw new RuntimeException("发送写指令失败！");
        }
    }
    /**
     * @description: 发送READ指令
     * @author: fansea
     * @date: 2024/7/29 14:31
     * @param: [serialPort, writeBuffer]
     * @return: byte[]
     **/
    public static byte[] sendReadCommand(SerialPort serialPort, byte[] writeBuffer) {
        // 例如：05 03 00 02 00 01 24 4E 返回：05 03 02 00 00 49 84
        System.out.println("发送读指令: " + bytesToHex(writeBuffer));
        if (serialPort == null) {
            throw new RuntimeException("串口未打开！");
        }
        //解析读指令的读取寄存器的数量由writeBuffer[4]writeBuffer[5]组成,这个字节数用来判断响应回来的数据占用多少字节，也能确定需要读取串口数据的长度
        /*注：返回数据字节总数 = 读取寄存器数量 x 2*/
        int registerNum = toUnsignedInt(Arrays.copyOfRange(writeBuffer, 4, 6)); //获取寄存器数量,不包括索引6
        int readLength = 5 + registerNum * 2; //获取返回Modbus指令长度
        serialPort.writeBytes(writeBuffer, writeBuffer.length);
        byte[] bytes = new byte[readLength];
        serialPort.readBytes(bytes, readLength);
        // 校验CRC码 PASS(×)
        // 截取返回读取的字节数组
        return Arrays.copyOfRange(bytes, 3, bytes.length-2);
    }
    public static byte[] intToByte(int value){
        byte[] bytes = new byte[2];
        bytes[0] = (byte) (value >> 8); // 获取高8位
        bytes[1] = (byte) (value & 0xFF); // 获取低8位
        return bytes;
    }

    public static void main(String[] args) {
        int unsignedInt = toUnsignedInt(new byte[]{0x01, 0x01});
        System.out.println("unsignedInt = " + unsignedInt);
    }

    /**
     * 将两个字节（高8位和低8位）转换为一个无符号16位整数的十进制表示。
     *
     * @param bytes 一个包含两个元素的字节数组，其中第一个元素是高8位，第二个元素是低8位。
     * @return 转换后的十进制数值。
     */
    public static int toUnsignedInt(byte[] bytes) {
        if (bytes == null || bytes.length != 2) {
            throw new IllegalArgumentException("Array must contain exactly two elements.");
        }

        // 将高8位字节左移8位，然后加上低8位字节
        return ((bytes[0] & 0xFF) << 8) + (bytes[1] & 0xFF);
    }


    /**
     * 校验Modbus指令中的CRC-16值。
     *
     * @param bytes 包含Modbus指令的字节数组。
     * @return 如果CRC校验成功则返回true，否则返回false。
     */
    public static boolean validateCRC16(byte[] bytes) {
        if (bytes == null || bytes.length < 2) {
            throw new IllegalArgumentException("Data array must not be null and should have at least two bytes for CRC.");
        }

        byte[] dataToCheck = Arrays.copyOfRange(bytes, 0, bytes.length - 2);
        int calculatedCRC = CRC16ModbusUtil.getCRC16(dataToCheck,dataToCheck.length);

        // Extract the last two bytes as the received CRC
        int receivedCRC = ((bytes[bytes.length - 2] & 0xFF) << 8) | (bytes[bytes.length - 1] & 0xFF);

        return calculatedCRC == receivedCRC;
    }

}